﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Threading.Tasks;
using Elderos.Highlight.Search;
using Elderos.Ontology;
using Elderos.Utils;
using Elderos.Utils.Extensions;

namespace Elderos.Highlight.Rules
{
    [RankingRule("NestedGeo", RuleType.LinkedEntities)]
    public class RuleNestedGeo : RankingRule
    {
        private readonly int _usaID = int.Parse(ConfigurationManager.AppSettings["UsaID"]);

        public RuleNestedGeo(IOntologyData ontology) : base(ontology) { }

        #region RankingRule members

        protected async override Task<Dictionary<TextEntity, IRuleResult>> InnerRank(HighlightResult highlightResult)
        {
            var results = new Dictionary<TextEntity, IRuleResult>();

            var entityPositions = new List<KeyValuePair<TextEntity, Position>>();
            foreach (var position in highlightResult.Positions.SelectMany(x => x.SubpositionsFlattened))
            {
                entityPositions.AddRange(
                    position.Entities.Select(x => new KeyValuePair<TextEntity, Position>(x, position)));
            }
            int attid = Ontology.Attributes.First(x => x.Name == "/location/location/containedby").ID;
            int[] entityIds = highlightResult.Positions.SelectMany(x => x.EntitiesFlattened)
                                                .Select(x => x.EntityID)
                                                .Distinct()
                                                .ToArray();

            var attRefs = await Ontology.GetAttRefsAsync(new[] {attid}, entityIds);
            
            //ключ - EntityID, значение - список геообъектов, где сущность содержится
            Dictionary<int, HashSet<int>> containingRelations = attRefs
                    .Select(x => new {x.EntityID, ContainedBy = x.ReferenceID})
                    .GroupBy(x => x.EntityID)
                    .ToDictionary(x => x.Key, y => new HashSet<int>(y.Select(z => z.ContainedBy)));
            foreach (var kv in entityPositions)
            {
                HashSet<int> containedBy;
                if (!containingRelations.TryGetValue(kv.Key.EntityID, out containedBy)) continue;

                IRuleResult entityResult = ProcessTextEntity(entityPositions, kv.Value, containedBy);

                results.Add(kv.Key, entityResult);
            }

            return results;

        }

        private readonly string[] _factorNames =
        {
            "NestedGeo_SumCoefs",
            "NestedGeo_TotalCount"
        };

        public override IRuleResult EmptyResult
        {
            get { return new ArrayResult(_factorNames, new[]{0D, 0}); }
        }

        private IRuleResult ProcessTextEntity(IEnumerable<KeyValuePair<TextEntity, Position>> entityPositions, 
                                       Position position,
                                       HashSet<int> containedBy) 
        {
            Interval<int> tokenInterval = position.TokenInterval;
            double[] containingCoefs =
                 (from epos in entityPositions
                 where containedBy.Contains(epos.Key.EntityID)
                 where epos.Key.EntityID != _usaID
                 let containedByInterval = epos.Value.TokenInterval
                 
                 where !epos.Value.TokenInterval.IsIntersection(tokenInterval)
                 select
                     tokenInterval.First > containedByInterval.First
                         ? tokenInterval.First - containedByInterval.Last
                         : tokenInterval.Last - containedByInterval.First
                 into distance
                 select GetCoef(distance)).ToArray();
            return new ArrayResult(_factorNames, new []{containingCoefs.Sum(), containingCoefs.Length});
        }

        public static double GetCoef(int distance)
        {
            //после этой дистанции функция быстро убывает почти к нулю
            const int meaningDistance = 7;
            distance = Math.Abs(distance);
            return 1 - MathExtended.NormalizedArcctg(distance, meaningDistance);
        }

        #endregion
    }
}
